package parser;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Class that implements the lexer 
 */
public class Lexer implements Iterator<Token> {

	/**
	 * Source code as String
	 */
	private String programCode;
	
	/**
	 * Source code as character array
	 */
	private char[] characterArray;
	
	/**
	 * List of the tokens which represents the source code
	 */
	private List<Token> tokenList = new ArrayList<Token>();
	
	/**
	 * Iterator, provides an interface to use this lexer in a compiler (or parser)
	 */
	private Iterator<Token> myIterator;
	
	/**
	 * Internal pointer to the current character
	 */
	private int charArrayPointer = 0;
	
	/**
	 * Line position in the source code 
	 * of the token which represents the current character
	 */
	private int col = 1;
	
	/**
	 * Column position in the source code 
	 * of the token which represents the current character
	 */
	private int line = 1;
	
	/**
	 * Array that provides all possible Token Types
	 */
	private TokenType[] allTypes = TokenType.values();

	/**
	 * Constructor, lexes the given source code and provides it for other classes by
	 * implementing an iterator and filling it
	 * @param programCode the source code
	 */
	public Lexer(String programCode) {
		this.programCode = programCode;
		this.characterArray = this.programCode.toCharArray();
		this.charArrayPointer = 0;
		read();
		this.myIterator = this.tokenList.iterator();
	}

	/**
	 * Method who skips all white spaces like \n, \t, " " 
	 * at the current position in the source code
	 */
	private void skipWhiteSpace() {
		if (this.characterArray[this.charArrayPointer] == '\n') {
			this.charArrayPointer++;
			this.col = 1;
			this.line++;
		} else if (this.characterArray[this.charArrayPointer] == '\t') {
			this.charArrayPointer++;
			this.col += 4;
		} else if (this.characterArray[this.charArrayPointer] == ' ') {
			this.charArrayPointer++;
			this.col++;
		}
	}

	/**
	 * Skips all white spaces or comment beginning at the current position
	 */
	private void skipWhiteSpaceAndComments() {
		try {
			while (Character
					.isWhitespace(this.characterArray[this.charArrayPointer])) {
				skipWhiteSpace();
				if (this.charArrayPointer >= this.characterArray.length)
					break;
			}
		}
		catch(ArrayIndexOutOfBoundsException e) {
			return;
		}
	}

	/**
	 * Reads a single Token that begins at the current position.
	 * This may be literals like integer, long, float, double, char, String literals.
	 * But also key words like return or any identifier
	 * Before this method returns, it increases line, col, charArrayPointer 
	 * 
	 * @return the token that starts at the current position
	 */
	private Token readSingleToken() {
		// Parsing Identifier, an identifier is something like that
		// identifier := ( _ | number | letter ) [identifier]
		if ((Character.isLetter(this.characterArray[this.charArrayPointer]))
				|| (this.characterArray[this.charArrayPointer] == '_')) {
			StringBuilder builder = new StringBuilder();
			while (Character
					.isLetterOrDigit(this.characterArray[this.charArrayPointer])
					|| (this.characterArray[this.charArrayPointer] == '_')) {
				builder.append(this.characterArray[this.charArrayPointer]);
				this.charArrayPointer++;
				this.col++;
				if (this.charArrayPointer >= this.characterArray.length)
					break;
			}
			String text = builder.toString().intern();
			for (TokenType tt : this.allTypes) {
				if (tt.isKeyword() && text.equals(tt.getText())) {
					return new Token(tt, text);
				}
			}
			return new Token(TokenType.IDENT, text);
		}
		// END parsing identifiers

		// Start parsing numberLiterals like float, double, int, long
		else if (Character.isDigit(this.characterArray[this.charArrayPointer])) {
			StringBuilder builder = new StringBuilder();
			while (Character
					.isDigit(this.characterArray[this.charArrayPointer])) {
				builder.append(this.characterArray[this.charArrayPointer]);
				this.charArrayPointer++;
				this.col++;
				if (this.charArrayPointer >= this.characterArray.length)
					break;
			}
			String text = builder.toString().intern();
			return new Token(TokenType.INT_LITERAL, text);
		}
		// END parsing numbers

		// Parsing other Tokens, like seperators
		else {
			for (int i = getMaxTokenType(); i > 0; i--) {
				try {
					StringBuilder builder = new StringBuilder();
					for (int j = 0; j < i; j++) {
						builder
								.append(this.characterArray[this.charArrayPointer
										+ j]);
					}
					String test = builder.toString().intern();
					for (TokenType tt : this.allTypes) {
						if (tt.getText().equals(test)) {
							this.charArrayPointer += i;
							this.col += i;
							return new Token(tt, "");
						}
					}
				} catch (ArrayIndexOutOfBoundsException e) {
				}
			}
			this.charArrayPointer++;
			this.col++;
			return new Token(TokenType.ERR, "");
		}
		// END parsing other tokens
	}

	/**
	 * Returns the maximal possible length of a keyword Token
	 * @return the maximal length
	 */
	private int getMaxTokenType() {
		int result = 0;
		for (TokenType tt : this.allTypes) {
			if ((tt.getText().length() > result) && tt.isKeyword()) {
				result = tt.getText().length();
			}
		}
		return result;
	}

	/**
	 * Transforms the source code to a List of Tokens
	 * Catches all Exception and converts them to an internal compiler problem
	 */
	private void read() {
		try {
			while (this.charArrayPointer < this.characterArray.length) {
				this.skipWhiteSpaceAndComments();
				if (this.charArrayPointer < this.characterArray.length) {
					this.tokenList.add(readSingleToken());
				}
			}
			this.tokenList.add(new Token(TokenType.EOF, ""));
		}
		catch (Exception e) {
			System.err.println("The lexer causes an internal problem. Please ask " +
					"the project members or ask at saarinformatix [at] googlemail [dot] com");
			System.exit(-1);
		}
	}

	/**
	 * Returns the tokenList and so it provides an alternative interface to provide the tokens
	 * (without using an iterator=
	 * @return the lexed Token-List
	 */
	public List<Token> getTokenList() {
		return this.tokenList;
	}
	
	/* (non-Javadoc)
	 * @see java.util.Iterator#hasNext()
	 */
	public boolean hasNext() {
		return this.myIterator.hasNext();
	}

	/* (non-Javadoc)
	 * @see java.util.Iterator#next()
	 */
	public Token next() {
		return this.myIterator.next();
	}

	/* (non-Javadoc)
	 * @see java.util.Iterator#remove()
	 */
	public void remove() {
		throw new UnsupportedOperationException(
				"Lexer doesn't support the remove option.");
	}

}
